﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
namespace Common.OjbectPool
{
    public class GenericObjectPool<T> : BaseGenericObjectPool<T>, IObjectPool<T>
    {
        private long createCount;//atomic
        private readonly IPooledObjectFactory<T> factory;
        private readonly IDictionary<T, IPooledObject<T>> allObjects = new ConcurrentDictionary<T, IPooledObject<T>>();
        private readonly BlockingCollection<IPooledObject<T>> idleObjects = new BlockingCollection<IPooledObject<T>>();
        private readonly Object evictionLock = new Object();
        private IEnumerator<IPooledObject<T>> evictionIterator; // @GuardedBy("evictionLock")

        public GenericObjectPool(IPooledObjectFactory<T> factory)
            : this(factory, new BaseObjectPoolConfig(), new DefaultEvictionPolicy<T>())
        {

        }

        public GenericObjectPool(IPooledObjectFactory<T> factory, BaseObjectPoolConfig config)
            : base(config, new DefaultEvictionPolicy<T>())
        {
            this.factory = factory;
        }
        public GenericObjectPool(IPooledObjectFactory<T> factory, BaseObjectPoolConfig config, IEvictionPolicy<T> evictionPolicy)
            : base(config, evictionPolicy)
        {
            this.factory = factory;
        }


        protected override void Evict()
        {
            AssertOpen();

            if (idleObjects.Count > 0)
            {

                IPooledObject<T> underTest = null;
                IEvictionPolicy<T> evictionPolicy = EvictionPolicy;

                lock (evictionLock)
                {
                    EvictionConfig evictionConfig = new EvictionConfig(
                           Config.MinEvictableIdleTimeMillis,
                          Config.MinIdle);

                    bool testWhileIdle = Config.TestWhileIdle;

                    for (int i = 0, m = Config.NumTestsPerEvictionRun; i < m; i++)
                    {
                        if (evictionIterator == null || !evictionIterator.MoveNext())
                        {
                            evictionIterator = (idleObjects as IEnumerable<IPooledObject<T>>).GetEnumerator();
                        }
                        if (!evictionIterator.MoveNext())
                        {
                            // Pool exhausted, nothing to do here
                            return;
                        }

                        try
                        {
                            underTest = evictionIterator.Current;
                        }
                        catch (NoSuchElementException nsee)
                        {
                            // Object was borrowed in another thread
                            // Don't count this as an eviction test so reduce i;
                            i--;
                            evictionIterator = null;
                            continue;
                        }

                        if (!underTest.StartEvictionTest())
                        {
                            // Object was borrowed in another thread
                            // Don't count this as an eviction test so reduce i;
                            i--;
                            continue;
                        }

                        if (evictionPolicy.Evict(evictionConfig, underTest,
                                idleObjects.Count))
                        {
                            Destroy(underTest);

                        }
                        else
                        {
                            if (testWhileIdle)
                            {
                                bool active = false;
                                try
                                {
                                    factory.ActivateObject(underTest);
                                    active = true;
                                }
                                catch (Exception e)
                                {
                                    Destroy(underTest);

                                }
                                if (active)
                                {
                                    if (!factory.ValidateObject(underTest))
                                    {
                                        Destroy(underTest);

                                    }
                                    else
                                    {
                                        try
                                        {
                                            factory.PassivateObject(underTest);
                                        }
                                        catch (Exception e)
                                        {
                                            Destroy(underTest);

                                        }
                                    }
                                }
                            }
                            if (!underTest.EndEvictionTest(idleObjects))
                            {
                                // TODO - May need to add code here once additional
                                // states are used
                            }
                        }
                    }
                }
            }

        }

        protected override void EnsureMinIdle()
        {
            EnsureIdle(Config.MinIdle);
        }

        public T BorrowObject()
        {
            return BorrowObject(Config.MaxWaitMillis);
        }


        public T BorrowObject(long borrowMaxWaitMillis)
        {
            AssertOpen();

            IPooledObject<T> p = null;

            // Get local copy of current config so it is consistent for entire
            // method execution
            bool blockWhenExhausted = Config.BlockWhenExhausted;

            while (p == null)
            {
                bool create = false;
                if (blockWhenExhausted)
                {
                    idleObjects.TryTake(out p);
                    if (p == null)
                    {
                        create = true;
                        p = Create();
                    }
                    if (p == null)
                    {
                        if (borrowMaxWaitMillis < 0)
                        {
                            idleObjects.TryTake(out p);
                        }
                        else
                        {
                            idleObjects.TryTake(out p, (int)borrowMaxWaitMillis);

                        }
                    }
                    if (p == null)
                    {
                        throw new NoSuchElementException(
                                "Timeout waiting for idle object");
                    }
                    if (!p.Allocate())
                    {
                        p = null;
                    }
                }
                else
                {
                    idleObjects.TryTake(out p);
                    if (p == null)
                    {
                        create = true;
                        p = Create();
                    }
                    if (p == null)
                    {
                        throw new NoSuchElementException("Pool exhausted");
                    }
                    if (!p.Allocate())
                    {
                        p = null;
                    }
                }

                if (p != null)
                {
                    try
                    {
                        factory.ActivateObject(p);
                    }
                    catch (Exception e)
                    {
                        try
                        {
                            Destroy(p);
                        }
                        catch (Exception e1)
                        {
                            // Ignore - activation failure is more important
                        }
                        p = null;
                        if (create)
                        {
                            NoSuchElementException nsee = new NoSuchElementException(
                                    "Unable to activate object", e);

                            throw nsee;
                        }
                    }
                    if (p != null && (Config.TestOnBorrow || create && Config.TestOnCreate))
                    {
                        bool validate = false;
                        Exception validationThrowable = null;
                        try
                        {
                            validate = factory.ValidateObject(p);
                        }
                        catch (Exception t)
                        {

                            validationThrowable = t;
                        }
                        if (!validate)
                        {
                            try
                            {
                                Destroy(p);

                            }
                            catch (Exception e)
                            {
                                // Ignore - validation failure is more important
                            }
                            p = null;
                            if (create)
                            {
                                NoSuchElementException nsee = new NoSuchElementException(
                                        "Unable to validate object", validationThrowable);

                                throw nsee;
                            }
                        }
                    }
                }
            }

            return p.GetObject();
        }

        public void ReturnObject(T obj)
        {
            IPooledObject<T> p = allObjects[obj];

            {
                if (p == null)
                {
                    return;  // Object was abandoned and removed
                }
                else
                {
                    // Make sure object is not being reclaimed
                    lock (p)
                    {
                        PooledObjectState state = p.GetState();
                        if (state != PooledObjectState.ALLOCATED)
                        {
                            throw new IllegalStateException(
                                    "Object has already been retured to this pool or is invalid");
                        }
                        else
                        {
                            p.MarkReturning(); // Keep from being marked abandoned
                        }
                    }
                }
            }
            if (Config.TestOnReturn)
            {
                if (!factory.ValidateObject(p))
                {
                    try
                    {
                        Destroy(p);
                    }
                    catch (Exception e)
                    {
                        SwallowException(e);
                    }
                    try
                    {
                        EnsureIdle(1);
                    }
                    catch (Exception e)
                    {
                        SwallowException(e);
                    }

                    return;
                }
            }

            try
            {
                factory.PassivateObject(p);
            }
            catch (Exception e1)
            {
                SwallowException(e1);
                try
                {
                    Destroy(p);
                }
                catch (Exception e)
                {
                    SwallowException(e);
                }
                try
                {
                    EnsureIdle(1);
                }
                catch (Exception e)
                {
                    SwallowException(e);
                }

                return;
            }

            if (!p.Deallocate())
            {
                throw new IllegalStateException(
                        "Object has already been retured to this pool or is invalid");
            }

            int maxIdleSave = Config.MaxIdle;
            if (IsClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.Count)
            {
                try
                {
                    Destroy(p);
                }
                catch (Exception e)
                {
                    SwallowException(e);
                }
            }
            else
            {
                idleObjects.TryAdd(p);
            }

        }

        public void InvalidateObject(T obj)
        {
            IPooledObject<T> p = allObjects[obj];
            if (p == null)
            {

                throw new IllegalStateException(
                        "Invalidated object not currently part of this pool");

            }
            lock (p)
            {
                if (p.GetState() != PooledObjectState.INVALID)
                {
                    Destroy(p);
                }
            }
            EnsureIdle(1);
        }

        public void AddObject()
        {
            AssertOpen();
            if (factory == null)
            {
                throw new IllegalStateException(
                        "Cannot add objects without a factory.");
            }
            IPooledObject<T> p = Create();
            AddIdleObject(p);
        }

        public int GetNumIdle()
        {
            return idleObjects.Count;
        }

        public int GetNumActive()
        {
            return allObjects.Count - idleObjects.Count;
        }

        public void Clear()
        {
            IPooledObject<T> p = null;
            idleObjects.TryTake(out p);
            while (p != null)
            {
                try
                {
                    Destroy(p);
                }
                catch (Exception e)
                {
                    SwallowException(e);
                }
                idleObjects.TryTake(out p);
            }
        }
        private void Destroy(IPooledObject<T> toDestory)
        {
            toDestory.Invalidate();
            //TODO:remove from ideOjbects
            allObjects.Remove(toDestory.GetObject());
            try
            {
                factory.DestroyObject(toDestory);
            }
            finally
            {

                Interlocked.Decrement(ref createCount);
            }
        }

        private IPooledObject<T> Create()
        {
            int localMaxTotal = Config.MaxTotal;

            long newCreateCount = Interlocked.Increment(ref createCount);
            if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||
                    newCreateCount > Int32.MaxValue)
            {
                Interlocked.Decrement(ref createCount);
                return null;
            }

            IPooledObject<T> p;
            try
            {
                p = factory.MakeObject();
            }
            catch (Exception e)
            {
                Interlocked.Decrement(ref createCount);
                throw;
            }

            allObjects.Add(p.GetObject(), p);
            return p;
        }
        private void AddIdleObject(IPooledObject<T> p)
        {
            if (p != null)
            {
                factory.PassivateObject(p);
                idleObjects.TryAdd(p);
            }
        }
        private void EnsureIdle(int idleCount)
        {
            if (idleCount < 1 || IsClosed())
            {
                return;
            }
            while (idleObjects.Count < idleCount)
            {
                IPooledObject<T> p = Create();
                if (p == null)
                {
                    // Can't create objects, no reason to think another call to
                    // create will work. Give up.
                    break;
                }
                idleObjects.TryAdd(p);
            }
        }
        public void Dispose()
        {
            Close();
        }
        public override void Close()
        {
            if (IsClosed())
            {
                return;
            }
            lock (closeLock)
            {
                if (IsClosed())
                {
                    return;
                }

                // Stop the evictor before the pool is closed since evict() calls
                // assertOpen()
                StopEvictor();

                closed = true;
                // This clear removes any idle objects
                Clear();


                // Release any threads that were waiting for an object

            }
        }
    }
}
